//
// Created by Administrator on 2022/10/6 0006.
//
#include <onix/console.h>
#include <onix/string.h>
#include <onix/io.h>
#include <onix/device.h>
#include <onix/interrupt.h>

#pragma clang diagnostic ignored "-Wint-to-pointer-cast"
#define CRT_ADDR_REG        0x3D4
#define CRT_DATA_REG        0x3D5

#define CRT_START_ADDR_H    0xC
#define CRT_START_ADDR_L    0xD
#define CRT_CURSOR_H        0xE
#define CRT_CURSOR_L        0xF

#define MEM_BASE    0xB8000
#define MEM_SIZE    0x4000
#define MEM_END     (MEM_BASE + MEM_SIZE)
#define WIDTH       80
#define HEIGHT      25
#define ROW_SIZE    (WIDTH * 2)
#define SCR_SIZE    (ROW_SIZE * HEIGHT)

#define ASCII_NUL   0x00
#define ASCII_ENQ   0x05
#define ASCII_BEL   0x07
#define ASCII_BS    0x08
#define ASCII_HT    0x09
#define ASCII_LF    0x0A
#define ASCII_VT    0x0B
#define ASCII_FF    0x0C
#define ASCII_CR    0x0D
#define ASCII_DEL   0x0F

// 当前显示器开始的内存位置
static u32 screen;
// 光标内存位置
static u32 pos;
// 光标位置
static u16 x = 0, y = 0;
// 字符样式
static u8 attr = 7;
// 空格
static u16 erase = 0x0720;

static void get_screen()
{
    outb(CRT_ADDR_REG, CRT_START_ADDR_H);
    screen = inb(CRT_DATA_REG) << 8;
    outb(CRT_ADDR_REG, CRT_START_ADDR_L);
    screen |= inb(CRT_DATA_REG);

    screen <<= 1;
    screen += MEM_BASE;
}

static void set_screen()
{
    outb(CRT_ADDR_REG, CRT_START_ADDR_H);
    outb(CRT_DATA_REG, ((screen - MEM_BASE) >> 9) & 0XFF);
    outb(CRT_ADDR_REG, CRT_START_ADDR_L);
    outb(CRT_DATA_REG, ((screen - MEM_BASE) >> 1) & 0XFF);
}

static void get_cursor()
{
    outb(CRT_ADDR_REG, CRT_CURSOR_H);
    pos = inb(CRT_DATA_REG) << 8;
    outb(CRT_ADDR_REG, CRT_CURSOR_L);
    pos |= inb(CRT_DATA_REG);

    get_screen();
    pos <<= 1;
    pos += MEM_BASE;

    u32 delta = (pos - screen) >> 1;
    x = delta % WIDTH;
    y = delta / WIDTH;
}

static void set_cursor()
{
    outb(CRT_ADDR_REG, CRT_CURSOR_H);
    outb(CRT_DATA_REG, ((pos - MEM_BASE) >> 9) & 0XFF);
    outb(CRT_ADDR_REG, CRT_CURSOR_L);
    outb(CRT_DATA_REG, ((pos - MEM_BASE) >> 1) & 0XFF);
}

void console_clear()
{
    screen = MEM_BASE;
    set_screen();
    pos = MEM_BASE;
    set_cursor();

    u16 *ptr = (u16*)MEM_BASE;
    while (ptr < (u16*)MEM_END) {
        *ptr++ = erase;
    }
}

void console_init()
{
    console_clear();

    device_install(
        DEV_CHAR, DEV_CONSOLE,
        NULL, "console", 0,
        NULL, NULL, console_write);
}

static void com_bs()
{
    if (x){
        x --;
        pos -= 2;
        *(u16*)pos = erase;
    }
}

static void com_del()
{
    *(u16*)pos = erase;
}

static void com_cr()
{
    pos -= (x << 1);
    x = 0;
}

static void scroll_up()
{
    if (screen + SCR_SIZE + ROW_SIZE >= MEM_END) {
        memcpy((u32*)MEM_BASE, (u32*)screen, SCR_SIZE);
        pos -= (screen - MEM_BASE);
        screen = MEM_BASE;
    }
    u32 *ptr = (u32*)(screen + SCR_SIZE);
    for (int i = 0; i < WIDTH; ++i) {
        *ptr++ = erase;
    }
    screen += ROW_SIZE;
    pos += ROW_SIZE;
    set_screen();
}

static void com_lf()
{
    if (y + 1 < HEIGHT) {
        y ++;
        pos += ROW_SIZE;
        return;
    }
    scroll_up();
}

int console_write(void *dev, char *buf, u32 count)
{
    char c;
    char *ptr = (char *)pos;
    int32 nr = 0;
    bool state = interrupt_disable();
    while (nr ++ < count)
    {
        c = *buf++;
        switch (c) {
            case ASCII_NUL:
                break;
//            case ASCII_ENQ:
//                break;
//            case ASCII_BEL:
//                break;
            case ASCII_BS: com_bs(); break;
//            case ASCII_HT:
//                break;
            case ASCII_LF: com_lf(); com_cr(); break;
//            case ASCII_VT:
//                break;
            case ASCII_FF: com_lf(); break;
            case ASCII_CR: com_cr(); break;
            case ASCII_DEL: com_del(); break;
            default:
                if (x >= WIDTH) {
                    x -= WIDTH;
                    pos -= ROW_SIZE;
                    com_lf();
                }
                *ptr = c; *(ptr + 1) = (char)attr;
                ptr += 2; pos += 2; x++;
                break;
        }
    }
    set_interrupt_state(state);
    set_cursor();
    return nr;
}